﻿using System;
using System.Collections.Generic;
using CompleterLib.AbstractTypes;
using Roslyn.Compilers.CSharp;

namespace CompleterLib
{
    interface CompleterReturnValue { }
    internal class MinFutureScore : CompleterReturnValue
    {
        private readonly int score;

        public MinFutureScore(int score)
        {
            this.score = score;
        }

        public int Score { get { return score; } }

        public override string ToString()
        {
            return "MinFutureScore = " + Score;
        }
    }
    /// <summary>
    /// Stores the components used to compute the score for later analysis.
    /// </summary>
    public struct ScoreInfo {
        public int TypeDistance;
        public int AbstractTypeDistance;
        public int EmptyPrefix;
        public int Empty;
        public int EmptyValueType;

        public IEnumerable<int> AsEnumerable()
        {
            return new int[] {
                TypeDistance,
                // Leave this out for now because it is not computed correctly for query experiment
                //  (i.e. the current query is included in the abstract type info).
                //AbstractTypeDistance,
                EmptyPrefix,
                Empty,
                EmptyValueType,
            };
        }
    }
    public class Completion : CompleterReturnValue, IComparable<Completion>
    {
        private readonly string text;
        private readonly TypeSymbol type;
        private readonly int score;
        private readonly bool isLValue;
        private readonly bool isRValue;
        //private readonly bool isTypeOrNamespace;
        private readonly TypeTerm tt;

        private readonly ScoreInfo scoreInfo;
        // The symbol this outermost completion corresponds to, if any
        private readonly IMethodSymbolWrapper symbol;
        public IMethodSymbolWrapper Method { get { return symbol; } }

        internal Completion(string text, TypeSymbol type = null,
            bool isLValue = false, bool isRValue = true,
            int score = 0,
            TypeTerm tt = null,
            IMethodSymbolWrapper sym = null,
            ScoreInfo scoreInfo = default(ScoreInfo))
        {
            this.text = text;
            this.type = type;
            this.isLValue = isLValue;
            this.isRValue = isRValue;
            this.score = score;
            this.tt = tt;

            this.symbol = sym;
            this.scoreInfo = scoreInfo;
        }
        internal Completion(string text, TypeSymbol type, int score, TypeTerm tt = null)
            : this(text, type, false, true, score, tt) { }
        internal Completion(string text, TypeSymbol type, bool isLValue, bool isRValue, TypeTerm tt)
            : this(text, type, isLValue, isRValue, 0, tt) { }
        internal Completion(string text, TypeSymbol type, TypeTerm tt)
            : this(text, type, false, true, tt) { }
        internal Completion(string text, TypeTerm tt)
            : this(text, null, false, true, tt) { }
        internal Completion(ExpressionSyntax exp, SemanticModel semModel, TypeTerm tt = null)
            : this(exp.GetText(), semModel.GetSymbolInfo(exp).Symbol, tt)
        {
            this.type = semModel.GetTypeInfo(exp).Type;
        }

        internal Completion(string str, Symbol sym, TypeTerm tt = null)
            : this(str, tt)
        {
            if (sym == null)
            {
                // Use the defaults.
            }
            else if (sym is LocalSymbol)
            {
                this.isLValue = (sym as LocalSymbol).IsWriteable;
                this.isRValue = true;
                this.type = (sym as LocalSymbol).Type;
            }
            else if (sym is ParameterSymbol)
            {
                this.isLValue = true;
                this.isRValue = true;
                this.type = (sym as ParameterSymbol).Type;
            }
            else if (sym is FieldSymbol)
            {
                this.isLValue = !(sym as FieldSymbol).IsReadOnly;
                this.isRValue = true;
                this.type = (sym as FieldSymbol).Type;
            }
            else if (sym is PropertySymbol)
            {
                this.isLValue = !(sym as PropertySymbol).IsReadOnly;
                this.isRValue = !(sym as PropertySymbol).IsWriteOnly;
                this.type = (sym as PropertySymbol).Type;
            }
            else if (sym is MethodSymbol)
            {
                this.isLValue = false;
                this.isRValue = true;
                this.type = (sym as MethodSymbol).ReturnType;
            }
            else
            {
                throw new Exception("Unknown symbol type: " + sym.GetType());
            }
        }
        internal Completion(Symbol sym, TypeTerm tt = null)
            // TODO Right way to ".ToString()" a Symbol?
            : this(sym.IsStatic || !(sym is PropertySymbol || sym is FieldSymbol)
                ? (sym is ParameterSymbol ? sym.Name : sym.ToDisplayString())
                : "this." + sym.Name, sym, tt) { }
        internal Completion(Symbol sym, int score, TypeTerm tt = null)
            : this(sym, tt)
        {
            this.score = score;
        }

        public string CompletionText { get { return text; } }
        public TypeSymbol Type { get { return type; } }
        public int Score { get { return score; } }
        public bool IsLValue { get { return isLValue; } }
        public bool IsRValue { get { return isRValue; } }
        public bool AllowsLookups { get { return isRValue; } }
        internal TypeTerm TypeTerm { get { return tt; } }
        public ScoreInfo ScoreInfo { get { return scoreInfo; } }

        public override string ToString()
        {
            return "[" + Score + "] " + text;
        }

        public int CompareTo(Completion other)
        {
            if (this.score == other.score)
            {
                return this.text.CompareTo(other.text);
            }
            else
            {
                return this.score.CompareTo(other.score);
            }
        }
        public override bool Equals(object obj)
        {
            return obj is Completion && (obj as Completion).text.Equals(this.text);
        }
        public override int GetHashCode()
        {
            return text.GetHashCode();
        }

        internal Completion CloneWithScore(int newScore)
        {
            return new Completion(text, type, isLValue, isRValue, newScore, tt, symbol, scoreInfo);
        }
    }
    internal class EmptyCompletion : Completion
    {
        private EmptyCompletion() : base("") { }
        private static readonly EmptyCompletion unique = new EmptyCompletion();
        public static EmptyCompletion Instance { get { return unique; } }
    }

    internal class CompletionTextComparer : IEqualityComparer<CompleterReturnValue>
    {
        private static Lazy<CompletionTextComparer> instance =
            new Lazy<CompletionTextComparer>(() => new CompletionTextComparer());
        public static CompletionTextComparer Instance { get { return instance.Value; } }

        private CompletionTextComparer() { }

        bool IEqualityComparer<CompleterReturnValue>.Equals(CompleterReturnValue x, CompleterReturnValue y)
        {
            if (x is Completion && y is Completion)
            {
                return (x as Completion).CompletionText.Equals((y as Completion).CompletionText);
            }
            else if (x is MinFutureScore && y is MinFutureScore)
            {
                return (x as MinFutureScore).Score.Equals((y as MinFutureScore).Score);
            }
            else
            {
                return false;
            }
        }

        int IEqualityComparer<CompleterReturnValue>.GetHashCode(CompleterReturnValue obj)
        {
            if (obj is Completion)
            {
                return (obj as Completion).CompletionText.GetHashCode();
            }
            else
            {
                return (obj as MinFutureScore).Score.GetHashCode();
            }
        }
    }
}
